#include <iostream>
using namespace std;

class A {
public:
  int i;
  static const int ii=321;// 要想给static赋值，必须加const。
  // static int iii;// 要想static的成员变量的值能变化，就只能这样写，然后class外进行定义（必须不加static）：int A::iii;或者int A::iii=111;
  
  A() : i(10) {
    cout << "A()" << i << endl;
  }
  A(int a) : i(a) {
    cout << "A()" << i << endl;
  }
  // 拷贝构造
  // A(A& a)  {
  //   cout << "A()" << endl;
  //   // 相当于把 a的所有属性复制给 this，最后return this；
  // }
  virtual void f() {
    cout << "A::f()" << i << endl;
  }
  virtual ~A() {
    cout << "~A()" << endl;
  }
};

class B : public A {
public:
  int j;
  B() : j(20) {
    // cout << "B()" << j << endl;
  }
  virtual void f() {
    cout << "B::f()" << j << endl;
  }
};

class C {
public:
  int k;
  C() : k(30) {
    // cout << "B()" << j << endl;
  }
  void f() {
    cout << "C::f()" << k << endl;
  }
};


A func(A a) {
  return a;
}
A func1() {
  A a;
  a.i = 333;
  printf("%p\n", (int *)&a);
  return a;
}
int func2() {
  int a=33;
  return a;
}


/*class Animal {
public:
  Animal() {
    cout << "Animal" << endl;
  }
};

class Cat : public Animal {
public:
  int age;
  Cat(int a) {
    age = a;
    cout << "Cat" << endl;
  }
};*/

int main() {
  // 析构方法也应该是 virtual的。
  // 当不是virtual的时，是静态绑定，执行效率当然很高很高。

  // 如果类中有 引用变量 int & x;
  // 那么只有一个方法来给其赋值，就是在类的构造方法上：类名::类名(int& a):x(a) {}
  // 绝对不能在构造方法的方法体中给其赋值，那变成了 x = xxx; 这对引用变量来说是不行的。

  // 如果在函数（和类方法）返回引用类型的东西，return 的一定不能是函数内的局部变量。
  // 如果函数返回的是引用类型，那么 f() 可以做左值：f() = xxx;

  /*
  A a;
  B b;

  A* p = &b;
  p->f();
  // A的f()有 virtual，B的f()有没有virtual 都会执行B的f();
  // A的f()没有 virtual，B的f()有没有virtual 都会执行A的f();
  // 这说明，此时 *p 是看作A处理的。但是当执行f() 方法时，会根据A的f()是否有virtual来决定执行哪个f()。

  a = b;
  a.f();// A,B的f()都有virtual的情况下，执行A的f()；通过 . 调用不是动态绑定；
  // 这说明，b此时视为A来处理。

  p = &a;
  p->f();// 执行的是A的f()；
  // 这说明p指向的不是b。也就是说，a 里已经没有 b的特有信息了。
  */

  /*
  B b1;
  B b2;
  C c;
  int* p1 = (int *)&b1;
  int* p2 = (int *)&b2;
  int* p3 = (int *)&c;
  printf("%d\n", sizeof(b1));// 12;
  printf("%d\n", sizeof(b2));// 12;

  // 如果没有继承自A，并且C中没有virtual，结果是4;
  // 如果没有继承自A，但是C中有virtual，结果是8;
  // 如果继承自A，A中有virtual，C中没有virtual，结果是12；
  printf("%d\n", sizeof(c));
  printf("%d\n", *p1);// 通常来说是一个比较大的数字；这因为它是一个地址的编号；
  printf("%d\n", *p2);// 通常来说是一个比较大的数字；这因为它是一个地址的编号；
  printf("%d\n", *p3);// 当单独的C，并且没有virtual，那么结果正是30；
  printf("%d\n", *(p1 + 1));// 结果是 10，正是j；
  printf("%d\n", *(p2 + 1));// 结果是 10，正是j；
  // 问题就出来*p1,*p2,*p3上，可以看到由于受到 virtual的影响，导致值差别很大。
  // 可以么理解，当有virtual时，首地址int型数字是一个地址编号，根据该地址可以找到一张表；
  // 而且，根据 *p1和*p2 的值一致，可以推断出不同实例的表是一样的，也就是说，表是属于类的；
  // 这个表记录了类的方法，那么在调用方法时，就是根据这个表的结果来调用的。
  */

  /*
  A aa;
  B bb;
  A * q = &aa;
  q->f(); // 执行的自然是A的f()；
  // 如果我们想办法，把B的表交给A，那么调用的就是B的f()；
  int * r1 = (int *)&aa;
  int * r2 = (int *)&bb;
  *r1 = *r2;
  q->f();// 确实是执行了B的f();但是i是一个不是我们控制的数字，这是为什么呢？
  // 分析：
  //  aa 有两个 int 型，第一个是表，第二个是 i = 10；
  //  bb 有两个 int 型，第一个是表，第二个是 j = 20；
  //  此时需要注意：
  //    aa的地址假如是0001，那么i的地址就是0005；
  //    bb的地址假如是1101，那么i的地址就是1105，j的地址就是1109；
  //    假如A的表的编号是2201，B的表的编号是3301；
  //  通过表的赋值，执行了B的f()，此时希望找到j的地址来获取j的值。也就是bb的地址要加两次。
  //  但是 aa 中只有两个，一个是表，一个是i，加两次的话，就在i之后了，这块内存并不是我们的程序掌控的，所以内容是不确定的。
  //  这就是上面i的值比较怪异的原因。
  */
 
  // A a;
  // A b;
  // A c = a; // 创建c的时候，没有执行构造，但销毁的时候会执行析构。
  // A d = func(a); // 析构执行了5次，这说明除了abcd四个对象外，还有一个，正是函数func内的形参a，函数执行完毕，内部的a也会进行析构。
  // 如果函数的参数是 A a，这表明需要创建一个新的实例，所以会执行拷贝构造；
  // 如果函数的参数是指针变量或者应用变量，是不需要创建新实例的，所以也就不会执行拷贝构造。


  // 实例化的方式
  // 1，A a(11);
  // 2，A a = 11;
  // 基于此，A b = a; 可以理解为：实例化 b，参数是 a，所以，只要存在一个构造 A(const A& a) {}，那么此时就会执行该构造。
  // 同时，func(a) 接收到实参时也是同样的执行过程。
  // A(const A& a) 只要写了，就必须带有 &，可以没有 const，不管是否存在 A b = a;
  A a;
  // A b = a;

  // func(a) = a;// 不论是否存在拷贝构造，都是可以的。为什么 func(a) 能作为左值呢？

  printf("%p\n", func(a));
  // func2() = 444;// 这是不行的。
  // func1() = a; // 可以的。
  // printf("%d\n", func1().i); // 能得到 333；
  // 通过上面的比较，说明返回的 class 的实例，本质是指针。
  A b = func1();
  printf("%p\n", (int *)&b);// 可以看到这个值和func1()内的值是一样的。
  // 但是当存在拷贝构造的时候，则报错。这是由于 A b = func1(); 会执行拷贝构造，但是执行时需要一个具有名称的变量，不能使用无名的参数传递给拷贝构造。


  // 假如类有一个指针形式的成员变量，char * name;
  // A a("haha");
  // A b(a); 即使没有拷贝构造也可以这么些，这是因为这种写法相当于 A b = a;
  // 那么此时 a 和 b 的 name 都指向了同一块内存，那么对这两个对象进行析构（假设析构内 delete [] name）时，会对同一块内存重复delete，导致报错。
  // 任何解决这个问题？
  // 显式的写出来拷贝构造，在其内进行name的 strcpy 操作。
  // A(const A& a) {
  //    name = new char[::strlen(a.name) + 1];
  //    ::strcpy(name, a.name);
  //    // 相当于 this->name..., 最后return this；
  // }
  // 如果是 string name; 就不需要了。因为不同对象指向不同的地址。


  // static
  // 假如A中有private: static int i;（也就是说，这是声明）
  // 必须在class外写上 int A::i;（虽然这里没有static，但它确实是static的。这里也必须不能加static，因为一旦加上static， 就表示A的i只能在本文件内访问了。）
  // 否则编译就失败了。
  // 有句话说：class 的{} 内都是声明，必须在 .cpp 文件内进行定义。
  // 不过，如果在class内直接给i赋值，就可以了。
  // printf("%d\n", a.ii);
  // printf("%d\n", A::ii);
  // 类的静态函数，只能访问静态的成员变量。


  // Cat cat(12);

  return 0;
}